package com.lifang.framework.beans.factory.support;

import cn.hutool.core.lang.Assert;
import com.lifang.framework.beans.BeansException;
import com.lifang.framework.beans.factory.ConfigurableListableBeanFactory;
import com.lifang.framework.beans.factory.ObjectFactory;
import com.lifang.framework.beans.factory.config.BeanDefinitionRegistry;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * bean工厂实现基类
 */
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry, ConfigurableListableBeanFactory {

    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap<>(16);

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        Assert.isTrue(!beanDefinitionMap.containsKey(beanName), "cannot repeat registration");
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new BeansException("No bean named '" + beanName + "' is defined");
        }
        return beanDefinition;
    }

    /**
     * 提前实例化单例Bean对象
     */
    @Override
    public void preInstantiateSingletons() throws BeansException {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition definition = entry.getValue();
            if (definition.isSingleton() && getSingleton(entry.getKey()) == null) {
                getBean(entry.getKey());
            }
        }
    }

    /**
     * 注册上下文引用
     */
    @Override
    public void registerResolvableDependency(Class<?> dependencyType, Object autowiredValue) {
        Assert.notNull(dependencyType, "Dependency type must not be null");
        if (autowiredValue != null) {
            if (!(autowiredValue instanceof ObjectFactory || dependencyType.isInstance(autowiredValue))) {
                throw new IllegalArgumentException("Value [" + autowiredValue + "] does not implement specified dependency type [" + dependencyType.getName() + "]");
            }
            this.resolvableDependencies.put(dependencyType, autowiredValue);
        }
    }

    /**
     * 按照类型返回 Bean 实例
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> Map<String, T> getBeansOfType(Class<T> requiredType) throws BeansException {
        Assert.notNull(requiredType, "class requiredType cannot be empty");
        Map<String, T> result = new HashMap<>();
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            Class<?> beanClass = entry.getValue().getBeanClass();
            if (requiredType.isAssignableFrom(beanClass)) {
                result.put(entry.getKey(), (T) getBean(entry.getKey()));
            }
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    private <T> T resolveBean(Class<?> requiredType) {
        for (Map.Entry<Class<?>, Object> entry : this.resolvableDependencies.entrySet()) {
            if (requiredType.isAssignableFrom(entry.getValue().getClass())) {
                return (T) entry.getValue();
            }
        }
        List<BeanDefinition> beanDefinitions = beanDefinitionMap.values().stream().filter(item -> requiredType.isAssignableFrom(item.getBeanClass())).collect(Collectors.toList());
        if (beanDefinitions.isEmpty()) {
            throw new BeansException("No bean for " + requiredType.getSimpleName());
        }
        if (beanDefinitions.size() > 1) {
            throw new BeansException("Has multiple bean for " + requiredType.getSimpleName());
        }
        return (T) getBean(beanDefinitions.get(0).getBeanClassName());
    }

    /**
     * 根据注解返回bean
     */
    @Override
    public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) throws BeansException {
        Assert.notNull(annotationType, "annotation type not be null");
        Map<String, Object> beansWithAnnotation = new HashMap<>();
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            if (entry.getValue().getBeanClass().isAnnotationPresent(annotationType)) {
                beansWithAnnotation.put(entry.getKey(), getBean(entry.getKey()));
            }
        }
        return beansWithAnnotation;
    }

    @Override
    public String[] getBeanNamesForType(Class<?> requiredType) {
        Assert.notNull(requiredType, "requiredType class must not be null");
        List<String> result = new ArrayList<>();
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getBeanClass() != null && requiredType.isAssignableFrom(beanDefinition.getBeanClass())) {
                result.add(entry.getKey());
            }
        }
        return result.toArray(new String[0]);
    }

    /**
     * 获取指定类型的bean
     * 存在多个相同类型的bean时会抛出BeansException
     */
    @Override
    public <T> T getBean(Class<T> requiredType) throws BeansException {
        Assert.notNull(requiredType, "resolveBean must not be null");
        return resolveBean(requiredType);
    }

    @Override
    public Class<?> getType(String beanName) {
        Assert.notBlank(beanName, "beanName is required");
        BeanDefinition definition = beanDefinitionMap.get(beanName);
        Assert.notNull(definition, "noSuchBean " + beanName);
        return definition.getBeanClass();
    }

    /**
     * 返回注册表中所有的Bean名称
     */
    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }
}
